from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, TypeGuard, TypeVar

from vengine.ve_coord import Area2d, Point2d, Vec2d
from vengine.ve_event import T_EventHandler, VE_EventDispatcher
from vengine.ve_globals import VE_ObjectIdPool, get_group, has_object
from vengine.ve_logger import get_logger

if TYPE_CHECKING:
    from vengine.ve_group import VE_Group

_T = TypeVar("_T")

class VE_Object(ABC):
    """ Base class for object """
    
    def __init__(self, object_id: str) -> None:
        if has_object(object_id):
            get_logger().warn(f"Creating object with same id ({object_id}) will override previous one", stack_info=True)
        
        VE_ObjectIdPool[object_id] = self
        self.object_id: str = object_id
        
        self.__groups: list["VE_Group"] = []
    
    @abstractmethod
    def tick(self) -> None:
        """ Customizable method, automatically called after event VE_tick """
        
    def __str__(self) -> str:
        return f"<{self.__class__.__name__} {self.object_id}>"
    
    def __repr__(self) -> str:
        return f"<{self.__class__.__name__} {self.object_id}>"
    
    def __matmul__(self, T: type[_T]) -> TypeGuard[_T]:
        """ Syntactic sugar for checking if self is inherited from T, or the instance of T
        
        Usage::

            box = VE_Box(...) # Create a box instance, omitting the arguments
            print(box @ VE_Box) # True
            print(box @ VE_Object) # True (because VE_Box is inherited from VE_Object)
            print(box @ VE_Observerable) # False (because VE_Box is not the parent of VE_Observerable)
        """
        return isinstance(self, T)
    
    def add_to_group(self, *groups: "VE_Group") -> None:
        for group in groups:
            if group in self.__groups: continue
            
            group.add(self)
            self.__groups.append(group)
            
    def remove_from_group(self, *groups: "VE_Group") -> None:
        for group in groups:
            if group not in self.__groups: continue
            
            group.remove(self)
            self.__groups.remove(group)
    
    def kill(self) -> None:
        for group in self.__groups:
            group.remove(self)
        # Remove self from VE_ObjectIdPool
        VE_ObjectIdPool.pop(self.object_id, None)
    
    def __del__(self) -> None:
        self.kill()


class VE_Observerable(VE_Object):
    """ Base class for observerable objects """
    
    def __init__(self, object_id: str) -> None:
        super().__init__(object_id)
        
        # Add self to Visible group
        get_group("Visible").add(self)

    @abstractmethod
    def tick(self): ...
    
    @abstractmethod
    def display(self) -> None:
        """ Customizable method, automatically called after event VE_display """

class VE_Moveable(VE_Object):
    """ Base class for moveable objects """
    
    def __init__(
        self,
        object_id: str,
        
        pos: Point2d,
        speed: Vec2d = Vec2d((0 ,0))
    ) -> None:
        """
        Args:
            object_id (str): unique object id
            pos (Point2d): Initial position on the window.
            speed (Vec2d, optional): Initial speed. Defaults to Vec2d((0 ,0)) which means the object is still.
        """
        
        super().__init__(object_id)
        
        self.pos: Point2d = pos
        self.speed: Vec2d = speed
    
    def tick(self) -> None:
        self.pos += self.speed

class VE_Box(VE_Object):
    """ Base class for a rectangle box """
    
    def __init__(
        self,
        object_id: str,
        
        area: Area2d
    ) -> None:
        super().__init__(object_id)

        self.area: Area2d = area

    @abstractmethod
    def tick(self): ...

class VE_Clickable(VE_Box):
    """ Rectangle box which supports clicking events """
    
    def __init__(
        self,
        object_id: str,
        
        area: Area2d,
    ) -> None:
        super().__init__(object_id, area)
        
    def add_click_callback(self, handler: T_EventHandler) -> None:
        """Add a callback handler when self is being clicked,
        this is equivalent to defining a handler listening to event `object_id.click`
        """
        VE_EventDispatcher._add_handler(
            f"{self.object_id}.click",
            handler
        )
        
    def in_click_area(self, pos: Point2d) -> bool:
        """ Customizable method to test if pos is in click detection area """

        return pos in self.area
        
    def tick(self): ...

class VE_Draggable(VE_Box):
    """ Rectangle box which supports dragging events """
    
    def __init__(
        self,
        object_id: str,
        
        area: Area2d,
    ) -> None:
        super().__init__(object_id, area)

    def add_drag_callback(self, handler: T_EventHandler) -> None:
        """Add a callback handler when self is being dragged,
        this is equivalent to defining a handler listening to event `object_id.drag`
        """
        VE_EventDispatcher._add_handler(
            f"{self.object_id}.drag",
            handler
        )